﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MySql.Data.MySqlClient;
using System.Data;
using log4net;
using System.Reflection;
using System.Collections;
using System.IO;
using System.Diagnostics;

namespace ProjectBuilder
{
    [DbTable("project")]
    public class Project
    {
        /// <summary>
        /// Звідки завантажувати данні
        /// </summary>
        public string BaseUrl { get; private set; }

        /// <summary>
        /// Шлях по якому генерувалась документація
        /// </summary>
        public string BasePath { get; set; }

        /// <summary>
        /// Шлях куди зберігати сорси
        /// </summary>
        public string SourcePath { get; private set; }

        /// <summary>
        /// Id поточного білда
        /// </summary>
        public long? BuildId { get; private set; }

        public string Name { get; private set; }

        public long? ProjectId { get; private set; }

        public long? Revision { get; private set; }

        private bool _isSourcesChanged = false;

        public Dictionary<string, string> SourceFiles { get; private set; }

        private Dictionary<ProjectBuilder.PhpDocTypes.XmlClass, Class> _classes;

        public Project(string name, string basePath)
        {
            Name = name;
            loadFormDb();
            BasePath = basePath;
            //BaseUrl = BasePath + BaseUrl;
            //SourcePath = BasePath + SourcePath;
            var build = new Build();
            build.ProjectId = ProjectId;
            build.Revision = Revision;
            build.Save();
            BuildId = build.Id;
            //BaseUrl = url;
            SourceFiles = new Dictionary<string, string>();
            _classes = new Dictionary<ProjectBuilder.PhpDocTypes.XmlClass, Class>();
        }

        protected void loadFormDb()
        {
            var cmd = new MySqlCommand();
            cmd.CommandText = "SELECT `settings`.* FROM `settings` WHERE `settings`.`project_id` = (SELECT `id` FROM `" + this.getAttribute<DbTableAttribute>().TableName + "` WHERE `name` = '" + Name + "' LIMIT 1)";
            cmd.CommandType = CommandType.Text;
            cmd.Connection = Globals.Connection;
            var reader = cmd.ExecuteReader();
            if (!reader.HasRows)
                throw new Exception("Project with name " + Name + " not found");
            reader.Read();
            ProjectId = long.Parse(reader["project_id"].ToString());
            Revision = long.Parse(reader["revision"].ToString());
            BaseUrl = reader["result_path"].ToString();
            SourcePath = reader["highlighted_source_path"].ToString();
            reader.Close();
        }

        public void build()
        {
            if (!_isSourcesChanged)
            {
                LogManager.GetLogger("Main").Info("Project sources not changed...");
                return;
            }
            LogManager.GetLogger("Main").Info("Scan source files...");
            scanSourceFiles();
            clearNewFlag();
            LogManager.GetLogger("Main").Info("Parsing...");
            loadIndex();
        }

        /// <summary>
        /// Завантажує головний файл doxygen
        /// </summary>
        protected void loadIndex()
        {
            var sUrl = Globals.Project.BaseUrl + "index.xml";
            var doc = sUrl.parseUrl<ProjectBuilder.PhpDocTypes.Project>();


            Class classObj = null;
            foreach (ProjectBuilder.PhpDocTypes.XmlPackage xmlPackege in doc.Packages)
            {
                var xmlPackegeDoc = (Globals.Project.BaseUrl + xmlPackege.File).parseUrl<ProjectBuilder.PhpDocTypes.Package>();
                var package = new Package();
                package.Name = xmlPackegeDoc.Name;
                package.Subpackage = "";
                if (xmlPackegeDoc.Classes != null)
                {
                    foreach (ProjectBuilder.PhpDocTypes.SubPackage xmlSubPackaege in xmlPackegeDoc.Classes.Subpackages)
                    {
                        var subPackaege = new Package();
                        subPackaege.Name = xmlPackegeDoc.Name;
                        subPackaege.Subpackage = xmlSubPackaege.Name;
                        subPackaege.Save();
                        foreach (ProjectBuilder.PhpDocTypes.XmlClass xmlClass in xmlSubPackaege.Classes)
                        {
                            classObj = new Class((Globals.Project.BaseUrl + xmlClass.Path).parseUrl<ProjectBuilder.PhpDocTypes.ClassType>(), subPackaege);
                            _classes.Add(xmlClass, classObj);
                        }
                    }
                }
                if (xmlPackegeDoc.Interfaces != null)
                {                    
                    foreach (ProjectBuilder.PhpDocTypes.SubPackage xmlSubPackaege in xmlPackegeDoc.Interfaces.Subpackages)
                    {
                        var subPackaege = new Package();
                        subPackaege.Name = xmlPackegeDoc.Name;
                        subPackaege.Subpackage = xmlSubPackaege.Name;
                        subPackaege.Save();
                        foreach (ProjectBuilder.PhpDocTypes.XmlClass xmlClass in xmlSubPackaege.Classes)
                        {
                            classObj = new Class((Globals.Project.BaseUrl + xmlClass.Path).parseUrl<ProjectBuilder.PhpDocTypes.ClassType>(), subPackaege);
                            _classes.Add(xmlClass, classObj);
                        }
                    }
                }

                if (xmlPackegeDoc.Interfaces != null)
                {
                    foreach (ProjectBuilder.PhpDocTypes.XmlClass xmlClass in xmlPackegeDoc.Interfaces.List)
                    {
                        classObj = new Class((Globals.Project.BaseUrl + xmlClass.Path).parseUrl<ProjectBuilder.PhpDocTypes.ClassType>(), package);
                        _classes.Add(xmlClass, classObj);
                    }
                }
                if (xmlPackegeDoc.Classes != null)
                {
                    foreach (ProjectBuilder.PhpDocTypes.XmlClass xmlClass in xmlPackegeDoc.Classes.List)
                    {
                        classObj = new Class((Globals.Project.BaseUrl + xmlClass.Path).parseUrl<ProjectBuilder.PhpDocTypes.ClassType>(), package);
                        _classes.Add(xmlClass, classObj);
                    }
                }
            }

            foreach (ProjectBuilder.PhpDocTypes.XmlClass xmlClass in _classes.Keys)
            {
                classObj = _classes[xmlClass];
                var classDoc = (Globals.Project.BaseUrl + xmlClass.Path).parseUrl<ProjectBuilder.PhpDocTypes.ClassType>();

                if (classDoc.ClassTree != null)
                {
                    foreach (ProjectBuilder.PhpDocTypes.TreeClassType tt in classDoc.ClassTree)
                    {
                        if (tt.Distance == 0)
                            continue;
                        var ttObject = new ClassRefClass();
                        ttObject.ClassId = classObj.GetDBId();
                        ttObject.Parent = tt.Value;
                        ttObject.Distance = tt.Distance;
                        ttObject.Save();
                    }
                }

                if (classDoc.InheritedVariables != null)
                    classDoc.InheritedVariables.ForEach(i => new InheritedVariables(i, classObj.GetDBId()).Save());

                if (classDoc.InheritedConstants != null)
                    classDoc.InheritedConstants.ForEach(i => new InheritedConstants(i, classObj.GetDBId()).Save());

                if (classDoc.InheritedMethods != null)
                    classDoc.InheritedMethods.ForEach(i => new InheritedMethods(i, classObj.GetDBId()).Save());
            }


            sUrl = Globals.Project.BaseUrl + "errors.xml";
            var doc2 = sUrl.parseUrl<ProjectBuilder.PhpDocTypes.Errors>();
            foreach (ProjectBuilder.PhpDocTypes.FileError fileError in doc2.FileErrors)
            {
                long? file = null;
                try
                {
                    file = File.getBDIdByPath(fileError.Name);
                }
                catch
                {
                    continue;
                }
                if (fileError.Errors != null)
                {
                    foreach (ProjectBuilder.PhpDocTypes.Error xmlError in fileError.Errors)
                    {
                        var error = new Error();
                        error.Line = xmlError.Line;
                        error.Number = xmlError.Num;
                        error.Body = xmlError.Body;
                        error.Type = "error";
                        error.FileId = file;
                        error.Save();
                    }
                }
                if (fileError.Warnings != null)
                {
                    foreach (ProjectBuilder.PhpDocTypes.Warning xmlWarning in fileError.Warnings)
                    {
                        var error = new Error();
                        error.Line = xmlWarning.Line;
                        error.Number = xmlWarning.Num;
                        error.Body = xmlWarning.Body;
                        error.Type = "warning";
                        error.FileId = file;
                        error.Save();
                    }
                }
            }
        }

        public void parseUpdateResults()
        {
            if (!System.IO.File.Exists(BasePath + Globals.UpdateResultsFile))
                throw new Exception("File " + Globals.UpdateResultsFile + " with updating results not found in " + BasePath);
            var sr = new StreamReader(BasePath + Globals.UpdateResultsFile, true);
            string content = sr.ReadToEnd();
            sr.Close();
            _isSourcesChanged = content.Contains("Updated to revision");
        }

        public void clearNewFlag()
        {
            Assembly cls = this.GetType().Assembly;
            foreach (Type type in cls.GetTypes())
            {
                if (type.IsSubclassOf(typeof(BaseDocObject)) || type.IsSubclassOf(typeof(BaseRefObject)))
                {
                    LogManager.GetLogger("BaseObject").Debug("Find class: " + type.Name);
                    clearNewFlagForObject(type);
                }
            }
        }

        protected void scanSourceFiles()
        {
            var sourceFolder = new DirectoryInfo(Globals.Project.BaseUrl + "__filesource/");
            if (!Directory.Exists(Globals.Project.SourcePath))
                Directory.CreateDirectory(Globals.Project.SourcePath);
            foreach (FileInfo sourceFile in sourceFolder.GetFiles())
            {
                var sourceDoc = sourceFile.FullName.parseUrl<ProjectBuilder.PhpDocTypes.Source>();
                if (sourceDoc != null && sourceDoc.Value != null)
                    SourceFiles.Add(sourceDoc.Path, sourceFile.Name);
                var sw = new StreamWriter(Globals.Project.SourcePath + sourceFile.Name);
                sw.Write(sourceDoc.Value);
                sw.Close();
            }
        }

        private void clearNewFlagForObject(Type o)
        {
            var attr = o.getAttribute<DbTableAttribute>();
            if (attr == null)
            {
                throw new Exception("Invalid class for clear " + o.Name);
            }
            var cmd = new MySqlCommand();
            var query = "UPDATE `" + attr.TableName + "` SET `new` = 0;";
            cmd.CommandText = query;
            cmd.CommandType = CommandType.Text;
            cmd.Connection = Globals.Connection;
            LogManager.GetLogger("BasObject").Debug("SQL: " + query);
            cmd.ExecuteNonQuery();
        }
    }
}
